#ifndef PHP_MYSQL_CLASS
#define PHP_MYSQL_CLASS

#include "php.h"
#include <sys/time.h>
#include <mysql.h>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <functional>
#include <unordered_map>

#define FIELD_ATTR_UNSIGNED 1
#define FIELD_ATTR_ZEROFILL 2

#define SHOW_FIELDS_NAME  0
#define SHOW_FIELDS_TYPE  1
#define SHOW_FIELDS_NULL  2
#define SHOW_FIELDS_KEY   3
#define SHOW_FIELDS_VALUE 4
#define SHOW_FIELDS_EXTRA 5

#define TRANS_START_WITH_CONSISTENT_SNAPSHOT 1
#define TRANS_START_READ_WRITE               2
#define TRANS_START_READ_ONLY                4
#define TRANS_NO_EXCEPTIONS                  8
#define TRANS_SINGLE                         16

#define MAX_DEADLOCK_TRIES 10

using namespace std; 
static int le_mysql_result; 

zend_class_entry *php_mysql_ce, *php_mysql_exception_ce; 
zend_object_handlers php_mysql_object_handlers, php_mysql_exception_object_handlers; 

enum {
	MYSQL_ASSOC = 1, 
	MYSQL_NUM   = 2, 
	MYSQL_BOTH  = (MYSQL_ASSOC | MYSQL_NUM)
}; 

#define MYSQL_CLASS_NAME           EVILWORLDS_NS    "\\Mysql"
#define MYSQL_EXCEPTION_CLASS_NAME MYSQL_CLASS_NAME "\\Exception"

#ifdef ZTS
	#define EM_SG(v) TSRMG(evil_mysql_globals_id, zend_evil_mysql_globals *, v)
#else
	#define EM_SG(v) (evil_mysql_globals.v)
#endif

#define IS_START_OF_DIGIT(str, len) \
	(isdigit(str[0]) || \
		((str[0] == '-' || str[0] == '+') && 1 <= len && isdigit(str[1])))
#define EXCEPTION_MYSQL_ERROR(type) \
	zend_throw_exception_ex ( \
		php_mysql_exception_ce, mysql_errno(obj -> connect -> mysql) TSRMLS_CC, \
		(char *)"%s: %s", type, mysql_error(obj -> connect -> mysql) \
	); 
#define EXCEPTION_MYSQL_ERROR_VA(msg, args...) \
	if (obj -> connect -> mysql && mysql_errno(obj -> connect -> mysql)) { \
		zend_throw_exception_ex ( \
			php_mysql_exception_ce, mysql_errno(obj -> connect -> mysql) TSRMLS_CC, \
			(char *)msg ", mysql error: %s", ##args, mysql_error(obj -> connect -> mysql) \
		); \
	} else { \
		zend_throw_exception_ex ( \
			php_mysql_exception_ce, 0 TSRMLS_CC, (char *)msg, ##args \
		); \
	}
#define CHECK_MYSQL_CONNECTION(obj) \
	if (!obj -> connect || !obj -> connect -> mysql) { \
		zend_throw_exception_ex ( \
			php_mysql_exception_ce, 0 TSRMLS_CC, \
			(char *)"Connection not found!" \
		); \
		RETURN_NULL(); \
	}
#define FETCH_RESOURCE_LINK(var_name, res_name, return_null) \
	var_name = NULL; \
	if (res_name) { \
		ZEND_FETCH_RESOURCE_NO_RETURN( \
			var_name, MYSQL_RES *, &res_name, -1, \
			"evilworlds mysql result", le_mysql_result \
		); \
		if (!var_name) { \
			zend_throw_exception_ex ( \
				php_mysql_exception_ce, 0 TSRMLS_CC, \
				(char *)"Invalid mysql result link!" \
			); \
			if (return_null) \
				RETURN_NULL() \
		} \
	} else if (obj && obj -> last_result) { \
		int type; \
		var_name = (MYSQL_RES *) zend_list_find(obj -> last_result, &type); \
		if (!var_name || type != le_mysql_result) { \
			zend_throw_exception_ex ( \
				php_mysql_exception_ce, 0 TSRMLS_CC, \
				(char *)"Invalid mysql result link #%d (type = %d)!", \
				obj -> last_result, type \
			); \
			if (return_null) \
				RETURN_NULL() \
		} \
	} else { \
		zend_throw_exception_ex ( \
			php_mysql_exception_ce, 0 TSRMLS_CC, \
			(char *)"Mysql result link not found!" \
		); \
		if (return_null) \
			RETURN_NULL() \
	}
#if DEBUG
	#define EM_DEBUG(msg, args...) php_printf("[debug] " msg "\n", ##args); 
#else
	#define EM_DEBUG(msg, args...)
#endif

#define REPLACE_CONCAT_BLOCK(var_name) \
	if (var_name ## _len) { \
		memcpy(*text_ptr + _offset, var_name, var_name ## _len); \
		_offset += var_name ## _len; \
	}

struct MysqlTypesToMacros {
	const char *mysql_type;
	char macros_name;
};

const struct MysqlTypesToMacros mysql_types_to_macros[] = {
	{"bit",        'd'}, 
	{"tinyint",    'd'}, 
	{"smallint",   'd'}, 
	{"mediumint",  'd'}, 
	{"int",        'd'}, 
	{"integer",    'd'}, 
	{"bigint",     'd'}, 
	{"bool",       'b'}, 
	{"real",       'f'}, 
	{"double",     'f'}, 
	{"float",      'f'}, 
	{"decimal",    'f'}, 
	{"numeric",    'f'}, 
	{"date",       's'}, 
	{"time",       's'}, 
	{"timestamp",  's'}, 
	{"datetime",   's'}, 
	{"year",       's'}, 
	{"char",       's'}, 
	{"varchar",    's'}, 
	{"binary",     's'}, 
	{"varbinary",  's'}, 
	{"tinyblob",   's'}, 
	{"blob",       's'}, 
	{"mediumblob", 's'}, 
	{"longblob",   's'}, 
	{"tinytext",   's'}, 
	{"text",       's'}, 
	{"mediumtext", 's'}, 
	{"longtext",   's'}, 
}; 

typedef struct mysql_connect_meta {
	MYSQL         *mysql; 
	bool          is_persistent; 
	unsigned int  active_transaction_level; 
} mysql_connect_meta; 

struct php_mysql_object {
	zend_object std; 
	mysql_connect_meta *connect; 
	int last_result; 
	bool logging; 
	zval *log_queries; 
}; 

bool start_transaction(php_mysql_object *obj, long flags); 
bool commit_transaction(php_mysql_object *obj); 
bool rollback_transaction(php_mysql_object *obj); 
bool do_evilworlds_mysql_query(php_mysql_object *obj, const char *real_query, int real_query_length, MYSQL_RES **result); 
char *do_prepare_value(zval *value, char type, int *ret_len, zval *self); 
char *do_array_prepare(HashTable *data, int *length, const char *sep, int sep_len, char type, bool only_values, zval *self); 
int replace_expression (
	char **text_ptr, int text_length, int start, int len, 
	const char *text0 = NULL, unsigned short text0_len = 0, 
	const char *text1 = NULL, unsigned short text1_len = 0, 
	const char *text2 = NULL, unsigned short text2_len = 0, 
	const char *text3 = NULL, unsigned short text3_len = 0, 
	const char *text4 = NULL, unsigned short text4_len = 0, 
	const char *text5 = NULL, unsigned short text5_len = 0
); 

ZEND_BEGIN_MODULE_GLOBALS(evil_mysql)
	unordered_map<string, mysql_connect_meta *> persistent_list; 
	uint persistent_cnt; 
ZEND_END_MODULE_GLOBALS(evil_mysql)
ZEND_DECLARE_MODULE_GLOBALS(evil_mysql)

PHP_METHOD(Mysql, __construct); 
PHP_METHOD(Mysql, connect); 
PHP_METHOD(Mysql, setCharset); 
PHP_METHOD(Mysql, escape); 
PHP_METHOD(Mysql, implode); 
PHP_METHOD(Mysql, prepare); 
PHP_METHOD(Mysql, beginTransaction); 
PHP_METHOD(Mysql, commit); 
PHP_METHOD(Mysql, rollback); 
PHP_METHOD(Mysql, transaction); 
PHP_METHOD(Mysql, inTransaction); 
PHP_METHOD(Mysql, query); 
PHP_METHOD(Mysql, numRows); 
PHP_METHOD(Mysql, fetchArray); 
PHP_METHOD(Mysql, fetchRow); 
PHP_METHOD(Mysql, fetchAssoc); 
PHP_METHOD(Mysql, fetchAll); 
PHP_METHOD(Mysql, affectedRows); 
PHP_METHOD(Mysql, info); 
PHP_METHOD(Mysql, getInfo); 
PHP_METHOD(Mysql, result); 
PHP_METHOD(Mysql, free); 
PHP_METHOD(Mysql, insertId); 
PHP_METHOD(Mysql, ping); 
PHP_METHOD(Mysql, close); 
PHP_METHOD(Mysql, error); 
PHP_METHOD(Mysql, errno); 
PHP_METHOD(Mysql, setLoggingQueries); 
PHP_METHOD(Mysql, getQueriesList); 
PHP_METHOD(Mysql, resetQueriesList); 
PHP_METHOD(Mysql, getInstance); 
PHP_METHOD(Mysql, getMacrosByType); 
PHP_METHOD(Mysql, getTableFields); 
PHP_METHOD(Mysql, prepareIntValue); 
PHP_METHOD(Mysql, prepareUnsignedIntValue); 
PHP_METHOD(Mysql, prepareStringValue); 
PHP_METHOD(Mysql, prepareBoolValue); 
PHP_METHOD(Mysql, prepareFloatValue); 
PHP_METHOD(Mysql, prepareQueryValue); 
PHP_METHOD(Mysql, prepareTableNameValue); 
PHP_METHOD(Mysql, prepareValue); 

static inline void init_evilworlds_mysql_driver(); 
static inline void free_mysql_connect_meta(mysql_connect_meta *meta); 
static void free_evilworlds_mysql_result(zend_rsrc_list_entry *rsrc TSRMLS_DC); 
static inline mysql_connect_meta *evilworlds_mysql_close(mysql_connect_meta *meta); 
inline static unsigned long evilworlds_mysql_escape_string(mysql_connect_meta *conn, char *to, const char *from, unsigned long length); 
static inline void evilworlds_mysql_close_all_persistent(); 

#endif // PHP_MYSQL_CLASS
